/** registery.d - Provided a wrapper for Win32 Registery

  Written in the D programming language 1.0

Authors:	Wei Li <oldrev@gmail.com>
Copyright:	Copyright (c) 2007 by Wei Li.
License:	BSD
*/

module dotmars.platform.windows.registry;

version(Windows) {

import dotmars.base.stdtypes;
import dotmars.base.string;
import dotmars.base.convert;
import dotmars.text.utf;
import dotmars.platform.windows.core;
import dotmars.platform.windows.winconst;

private 
{

	const HKEY HKEY_CLASSES_ROOT =           cast(HKEY)(0x80000000);
	const HKEY HKEY_CURRENT_USER =           cast(HKEY)(0x80000001);
	const HKEY HKEY_LOCAL_MACHINE =          cast(HKEY)(0x80000002);
	const HKEY HKEY_USERS =                  cast(HKEY)(0x80000003);
	const HKEY HKEY_PERFORMANCE_DATA =       cast(HKEY)(0x80000004);

	const HKEY HKEY_CURRENT_CONFIG =         cast(HKEY)(0x80000005);
	const HKEY HKEY_DYN_DATA =               cast(HKEY)(0x80000006);

	enum
	{
		ERROR_SUCCESS =                    0,
		ERROR_INVALID_FUNCTION =           1,
		ERROR_FILE_NOT_FOUND =             2,
		ERROR_PATH_NOT_FOUND =             3,
		ERROR_TOO_MANY_OPEN_FILES =        4,
		ERROR_ACCESS_DENIED =              5,
		ERROR_INVALID_HANDLE =             6,
		ERROR_NO_MORE_FILES =              18,
		ERROR_MORE_DATA =		   234,
		ERROR_NO_MORE_ITEMS =		   259,
	}
}

////////////////////////////////////////////////////////////////////////////////

class RegistryException : Exception
{
    public this(string msg)
    {
        super(msg);
    }
}

////////////////////////////////////////////////////////////////////////////////

private HKEY RegOpenKey(HKEY hkey, string subkey, REGSAM sam)
{
    HKEY hret;
    wchar* name = null;

    LONG result = 
        RegOpenKeyExW(hkey, toUtf16z(subkey), 0, sam, &hret);
    if(result != ERROR_SUCCESS)
        throw new RegistryException("Failed to RegOpenKey");
    return hret;
}

private void RegClose(HKEY key)
{
   if(RegIsRootKey(key))
        return;

    RegCloseKey(key);
}

private Value RegQueryValue(HKEY hkey, string name)
{   
    DWORD type = 0, dataSize = 0;
    LONG r = RegQueryValueExW(hkey, toUtf16z(name), null, &type, null, &dataSize);
    if(r != ERROR_SUCCESS)
        throw new RegistryException("Failed to Query Value Size");

    ubyte[] data;
    data.length = dataSize;
    r = RegQueryValueExW(hkey, toUtf16z(name), null, &type, cast(char*)data.ptr, &dataSize);
    if(r != ERROR_SUCCESS)
        throw new RegistryException("Failed to Query Value Data");
    Value v = Value(data, type);
    return v;
}

private string RegEnumKey(HKEY hkey, DWORD index)
{
    wchar[256] name;
    DWORD chars = name.length;
    LONG result = 
        RegEnumKeyExW(hkey, index, name.ptr, &chars, null, null, null, null);
    if(result != ERROR_SUCCESS)
        throw new RegistryException("Failed to RegEnumKey");
    string ret =  toUtf8(name[0..chars]);
    return ret;
}

private HKEY RegCreateKey(HKEY hkey, string name)
{
		HKEY hr = null;
		LONG result = 
		RegCreateKeyExW(hkey, toUtf16z(name), 0, null, 0, 
			KEY_ALL_ACCESS, null, &hr, null);
		if(result != ERROR_SUCCESS)
			throw new RegistryException("Failed to RegCreateKey");
		return hr;
}

private string RegEnumValue(HKEY hkey, DWORD index, ubyte[] data = null)
{
    wchar[16384] name;
    DWORD chars = name.length;
    DWORD type = 0;
    DWORD dataSize;
    
    LONG result = 
        RegEnumValueW(hkey, index, name.ptr, &chars, null, &type, null, &dataSize);
    if(result != ERROR_SUCCESS)
        throw new RegistryException("Failed to EnumValue");
    return toUtf8(name[0..chars]);
}

private void RegQueryKeyInfo(HKEY hkey, out uint subkeys, out uint values)
{
    LONG result = 
        RegQueryInfoKeyW(hkey, null, null, null, 
                &subkeys, null, null, &values, null, null, null, null);
    if(result != ERROR_SUCCESS)
        throw new RegistryException("Failed to RegQueryInfoKey");
}

private void RegSetValue(HKEY hkey, string name, ubyte[] data, DWORD type)
{
	LONG result = RegSetValueExW(hkey, toUtf16z(name), 0, type, cast(char*)data.ptr, data.length);
	if(result != ERROR_SUCCESS)
		throw new RegistryException("Failed to RegSetValue");
}

private uint RegValueCount(HKEY hkey)
{
    uint keys, values;
    RegQueryKeyInfo(hkey, keys, values);
    return values;
}

private uint RegKeyCount(HKEY hkey)
{
    uint keys, values;
    RegQueryKeyInfo(hkey, keys, values);
    return keys;
}

private bool RegIsRootKey(HKEY hkey)
{
    bool ret;
    switch(cast(DWORD)hkey)
    {
        case cast(DWORD)HKEY_CLASSES_ROOT:
        case cast(DWORD)HKEY_CURRENT_USER:
        case cast(DWORD)HKEY_LOCAL_MACHINE:
        case cast(DWORD)HKEY_USERS:
            ret = true;
            break;

        default:
            ret = false;
            break;
    }
    return ret;
}

////////////////////////////////////////////////////////////////////////////////

struct Value
{
    enum TypeIndex 
    {
        Uint = 0,
		Ulong,
        String
    }

    private ubyte[] m_data;
    private DWORD   m_type;

    public static Value opCall(ubyte[] data, DWORD type)
    {
		Value v;
        v.m_data = data;
        v.m_type = type;
		return v;
    }

	public static Value opCall(DWORD data)
	{
		Value v;

		version(BigEndian)
		{
			v.m_type = REG_DWORD_BIG_ENDIAN;
		}
		else version(LittleEndian)
		{
			v.m_type = REG_DWORD;
		}

		v.m_data.length = DWORD.sizeof;
		DWORD* p = cast(DWORD*)(v.m_data.ptr);
		*p = data;
		return v;
	}

	public static Value opCall(ulong data)
	{
		Value v;
		v.m_type = REG_QWORD;
		v.m_data.length = ulong.sizeof;
		ulong* p = cast(ulong*)v.m_data.ptr;
		*p = data;
		return v;
	}

	public static Value opCall(ubyte[] data)
	{
		Value v;
		v.m_type = REG_BINARY;
		v.m_data = data.dup;
		return v;
	}

	public static Value opCall(string data)
	{
		Value v;
		v.m_type = REG_SZ;
		wstring wstr = toUtf16(data);
		wstr ~= '\0';
		v.m_data.length = wstr.length * wchar.sizeof;
		v.m_data[] = (cast(ubyte*)wstr.ptr)[0 .. v.m_data.length];		
		return v;
	}

	public DWORD type()
	{
		return m_type;
	}

	public ubyte[] data()
	{
		return m_data;
	}

    public string asString()
    {
        assert(m_type == REG_SZ || m_type == REG_MULTI_SZ);

        wchar* ptr = cast(wchar*)m_data.ptr;
        string ret = toUtf8(ptr);

        return ret;
    }

    public uint asUint()
    {
		assert(m_type == REG_DWORD || m_type == REG_DWORD_BIG_ENDIAN || 
				m_type == REG_DWORD_LITTLE_ENDIAN);

		DWORD *dw = cast(DWORD*)m_data.ptr;

		version(BigEndian)
		{
			if(m_type == REG_DWORD_BIG_ENDIAN)
				return *dw;
			else 
				return BitConverter.reverseByteOrder(*dw);
		}
		else version(LittleEndian)
		{
			if(m_type == REG_DWORD_BIG_ENDIAN)
				return BitConverter.reverseByteOrder(*dw);
			else
				return *dw;
		}

    }

	public ulong asUlong()
	{
		ulong *p = cast(ulong*)m_data.ptr;
		return *p;
	}
}

////////////////////////////////////////////////////////////////////////////////

static final class Registry
{
    private static Key m_hkcu = null;
    private static Key m_hklm = null;

    public static this() {
        m_hklm = new Key(HKEY_LOCAL_MACHINE, "HKEY_LOCAL_MACHINE");
        m_hkcu = new Key(HKEY_CURRENT_USER, "HKEY_CURRENT_USER");
    }

    private this() { }

    public static Key localMachine() {
        return m_hklm;
    }

    public static Key currentUser() {
        return m_hkcu;
    }

}

////////////////////////////////////////////////////////////////////////////////

class Key : IDisposable
{
    private string m_name;
    private HKEY    m_handle = null;

    private this(HKEY parent, string name)
    in {
        assert(name !is null);
    }
    body {
        m_handle = parent;
        m_name = name;
    }

    public ~this()
    {
        if(m_handle !is null)
            close();
    }

    public HKEY handle() { 
        return m_handle;
    }

    public override void close()
    {
        if(m_handle !is null)
            RegClose(m_handle);
    }

    public string name() {
        return m_name;
    }
	
	public Key createSubkey(string name)
	{
		HKEY hkey = RegCreateKey(m_handle, name);
		Key subkey = new Key(hkey, name);
		return subkey;
	}

    public Key openSubkey(string name, bool writable = false)
    {
		DWORD flags = KEY_READ;
		if(writable) flags |= KEY_WRITE;
        HKEY hkey = RegOpenKey(m_handle, name, flags);
        Key subkey = new Key(hkey, name);
        return subkey;
    }

    public Key openSubkey(uint index, bool writable = false)
    {
        string name = RegEnumKey(m_handle, index);
        return openSubkey(name, writable);
    }

	public void setValue(string name, Value val)
	{
		RegSetValue(m_handle, name, val.data, val.type);
	}

	public void setValue(DWORD dwdata)
	{
		ubyte[DWORD.sizeof] data;
		ubyte* ptr = cast(ubyte*)(&dwdata);
		data[] = ptr[0 .. data.length];
		RegSetValue(m_handle, name, data, REG_DWORD);
	}

	public void setValue(string name, ubyte[] data)
	{
		RegSetValue(m_handle, name, data, REG_BINARY);
	}

	public void setValue(string name, string data)
	{
		wstring wstr = toUtf16(data);
		wstr ~= '\0';
		ubyte[] wdata;
		wdata.length = wstr.length * wchar.sizeof;
		wdata[] = (cast(ubyte*)wstr.ptr)[0 .. wdata.length];		
		RegSetValue(m_handle, name, wdata, REG_SZ);
	}

    public Value getValue(string name)
    {
        return RegQueryValue(m_handle, name);
    }

    public Value getValue(uint index)
    {
        string valueName = RegEnumValue(handle, index);
        return RegQueryValue(handle, valueName);
    }

    public string getValueName(uint index)
    {
        return RegEnumValue(m_handle, index);
    }

    public string[] subkeyNames()
    {
        string[] ret;
        for(uint i = 0; i < subkeyCount; i++)
        {
            ret ~= getSubkeyName(i);
        }

        return ret;
    }

    public string getSubkeyName(uint index)
    {
        return RegEnumKey(m_handle, index);
    }

    public string[] valueNames()
    {
        string[] ret;
        for(uint i = 0; i < valueCount; i++)
        {
            ret ~= getValueName(i);
        }
        return ret;
    }

    public uint valueCount()
    {
        return RegValueCount(m_handle);
    }

    public uint subkeyCount()
    {
        return RegKeyCount(m_handle);
    }

version(Phobos) {
	public override string toString()
	{
		return name;
	}
} else version(Tango) {
	public override string toUtf8()
	{
		return name;
	}
}
else {
	static assert(false, "No support version of the runtime library");
}

}


}//version(Windows)
